﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Data;
using System.Data.SQLite;

namespace Lights
{
    /// <summary>
    /// Collezione di oggetti. 
    /// </summary>
    public class Collector<T> : IEnumerable<T>, INotifyCollectionChanged
    {
        #region Events
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        #endregion

        #region Properties

        #endregion

        #region Fields
        private SQLiteConnection connection;
        private string table;
        #endregion

        #region DBWrappers
        #endregion

        #region Methods
        // singleton class
        public Collector(string path)
        {
            try
            {
                connection = new SQLiteConnection(string.Format("Data Source = {0}/database.sqlite", path));
                connection.Open();
                table = typeof(T).Name;

                // se la tabella di nome (type) non esiste
                try
                {
                    var c = this[0];
                }
                catch
                {
                    using (SQLiteCommand cmd = new SQLiteCommand(string.Format("CREATE TABLE {0}(key INTEGER PRIMARY KEY, value BLOB)", this.table), connection))
                        cmd.ExecuteNonQuery();
                }

            }
            catch (Exception e)
            {
                throw e;
            }
        }

        public bool Add(T value)
        {
            bool res = false;
            using (var command = connection.CreateCommand())
            {
                command.CommandText = string.Format("INSERT INTO {0}(key,value) VALUES(@key,@value)", this.table);
                command.Parameters.AddWithValue("@key", value.GetHashCode());
                command.Parameters.AddWithValue("@value", Serializer.Serialize(value));
                res = command.ExecuteNonQuery() == 1;
            }
            if(res != false && CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, value));
            return res;
        }

        public void AddRange(T[] values)
        {
            using (var command = connection.CreateCommand())
            {
                using (var tx = connection.BeginTransaction())
                {
                    command.CommandText = string.Format("INSERT INTO {0}(key,value) VALUES(@key,@value)", this.table);
                    for (int i = 0; i < values.Length; i++)
                    {
                        command.Parameters.Clear();
                        command.Parameters.AddWithValue("@key", values[i].GetHashCode());
                        command.Parameters.AddWithValue("@value", Serializer.Serialize(values[i]));
                        command.ExecuteNonQuery();
                    }
                    tx.Commit();
                }
            }
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, values));
        }

        public bool ContainsKey(int key)
        {
            using (SQLiteCommand cmd = new SQLiteCommand(string.Format("SELECT key FROM {0} WHERE key = {1}", this.table, key), connection))
            {
                return Convert.ToInt32(cmd.ExecuteScalar()) == key;
            }
        }

        public T this[int key]
        {
            get
            {
                using (SQLiteCommand cmd = new SQLiteCommand(string.Format("SELECT value FROM {0} WHERE key = {1}", this.table, key), connection))
                {
                    byte[] buffer = cmd.ExecuteScalar() as byte[];
                    return buffer != null ? (T)Serializer.Deserialize(buffer) : default(T);
                }
            }
            set
            {
                byte[] buffer = Serializer.Serialize(value);
                using (SQLiteCommand cmd = new SQLiteCommand(string.Format("UPDATE {0} SET value={2} WHERE key={1})", this.table, "@key", "@value"), connection))
                {
                    cmd.Parameters.Add("@value", DbType.Binary, buffer.Length);
                    cmd.Parameters["@key"].Value = key;
                    cmd.Parameters["@value"].Value = buffer;
                    cmd.ExecuteNonQuery();
                }
                if (CollectionChanged != null)
                    CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value));
            }
        }

        public void Clear()
        {
            using (SQLiteCommand cmd = new SQLiteCommand(string.Format("DELETE FROM {0}", this.table), connection))
                cmd.ExecuteNonQuery();
            if (CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public bool Remove(int key)
        {
            bool res = false;
            T value = this[key];
            using (SQLiteCommand cmd = new SQLiteCommand(string.Format("DELETE FROM {0} WHERE key = {1}", this.table, key), connection))
                res = cmd.ExecuteNonQuery() == 1;
            if (res && CollectionChanged != null)
                CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, value));
            return res;
        }

        public bool Remove(T item)
        {
            return Remove(item.GetHashCode());
        }

        public IEnumerator<T> GetEnumerator()
        {
            using (SQLiteCommand cmd = new SQLiteCommand(string.Format("SELECT value FROM {0}", this.table), connection))
            {
                using (var reader = cmd.ExecuteReader())
                {
                    if (reader.HasRows)
                    {
                        while (reader.Read())
                        {
                            byte[] buffer = reader.GetValue(0) as byte[];
                            yield return (T)Serializer.Deserialize(buffer);
                        }
                    }
                }
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion
    }
}
